Harvesting a Position
- Rust
- TypeScript Kit
- TypeScript Legacy
Harvesting a position in Orca Whirlpools allows you to collect any accumulated fees and rewards without closing the position. This process is useful when you want to claim your earnings while keeping your liquidity active in the pool, ensuring you continue to benefit from potential future fees.
1. Overview of Harvesting a Position
The SDK helps you generate the instructions needed to collect fees and rewards from a position without closing it. This allows you to realize your earnings while maintaining liquidity in the pool.
With this function, you can:
- Collect accumulated trading fees from your position.
- Harvest rewards earned by providing liquidity, all while keeping the position active.
2. Getting Started Guide
Step-by-Step Guide to Harvesting a Position
To harvest fees and rewards from a position, follow these steps:
- RPC Client: Use a Solana RPC client to interact with the blockchain.
- Position Mint: Provide the mint address of the NFT representing your position. This NFT serves as proof of ownership and represents the liquidity in the position.
- Authority: This can be your wallet, which will fund the pool initialization. If the authority is not specified, the default wallet will be used. You can configure the default wallet through the SDK.
- Create Instructions: Use the appropriate function to generate the necessary instructions to harvest fees and rewards.
use orca_whirlpools::{
harvest_position_instructions, set_whirlpools_config_address, WhirlpoolsConfigInput,
};
use solana_client::nonblocking::rpc_client::RpcClient;
use solana_sdk::pubkey::Pubkey;
use std::str::FromStr;
use solana_sdk::signature::Signer;
use orca_tx_sender::{
build_and_send_transaction,
set_rpc, get_rpc_client
};
use solana_sdk::commitment_config::CommitmentLevel;
use crate::utils::load_wallet;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
set_rpc("https://api.devnet.solana.com").await?;
set_whirlpools_config_address(WhirlpoolsConfigInput::SolanaDevnet).unwrap();
let wallet = load_wallet();
let rpc = get_rpc_client()?;
let position_mint_address =
Pubkey::from_str("HqoV7Qv27REUtmd9UKSJGGmCRNx3531t33bDG1BUfo9K").unwrap();
let result = harvest_position_instructions(&rpc, position_mint_address, Some(wallet.pubkey()))
.await?;
println!("Fees Quote: {:?}", result.fees_quote);
println!("Rewards Quote: {:?}", result.rewards_quote);
println!("Number of Instructions: {}", result.instructions.len());
let signature = build_and_send_transaction(
result.instructions,
&[&wallet],
Some(CommitmentLevel::Confirmed),
None, // No address lookup tables
).await?;
println!("Harvest transaction sent: {}", signature);
Ok(())
}
3. Usage Example
Suppose you are a developer creating a bot to manage investments for a group of investors. The bot periodically collects accumulated fees and rewards from liquidity positions to distribute profits among investors. By using the SDK, you can generate the instructions to collect earnings from each active position without closing it, allowing the liquidity to continue generating returns and potentially reinvest your earned fees into the position.
4. Next Steps
After harvesting fees and rewards, you can:
- Monitor Performance: Keep track of your position to evaluate future earnings and the overall performance.
- Reinvest Earnings: Use the harvested fees and rewards to add more liquidity or diversify your positions.
- Harvest Regularly: Regularly collect your earnings to maintain optimal capital efficiency while keeping your liquidity active.
By using the SDK, you can maximize the benefits of providing liquidity while keeping your position open and continuously earning fees.
Harvesting a position in Orca Whirlpools allows you to collect any accumulated fees and rewards without closing the position. This process is useful when you want to claim your earnings while keeping your liquidity active in the pool, ensuring you continue to benefit from potential future fees.
1. Overview of Harvesting a Position
The SDK helps you generate the instructions needed to collect fees and rewards from a position without closing it. This allows you to realize your earnings while maintaining liquidity in the pool.
With this function, you can:
- Collect accumulated trading fees from your position.
- Harvest rewards earned by providing liquidity, all while keeping the position active.
2. Getting Started Guide
Step-by-Step Guide to Harvesting a Position
To harvest fees and rewards from a position, follow these steps:
- RPC Client: Use a Solana RPC client to interact with the blockchain.
- Position Mint: Provide the mint address of the NFT representing your position. This NFT serves as proof of ownership and represents the liquidity in the position.
- Authority: This can be your wallet, which will fund the pool initialization. If the authority is not specified, the default wallet will be used. You can configure the default wallet through the SDK.
- Create Instructions: Use the appropriate function to generate the necessary instructions to harvest fees and rewards.
import { harvestPositionInstructions, setWhirlpoolsConfig } from '@orca-so/whirlpools';
import { createSolanaRpc, devnet, address } from '@solana/kit';
import { loadWallet } from './utils';
await setWhirlpoolsConfig('solanaDevnet');
const devnetRpc = createSolanaRpc(devnet('https://api.devnet.solana.com'));
const wallet = await loadWallet();
const positionMint = address("HqoV7Qv27REUtmd9UKSJGGmCRNx3531t33bDG1BUfo9K");
const { feesQuote, rewardsQuote, instructions, callback: sendTx } = await harvestPosition(
devnetRpc,
positionMint,
wallet
);
// Use the callback to submit the transaction
const txId = await sendTx();
console.log(`Fees owed token A: ${feesQuote.feeOwedA}`);
console.log(`Rewards '1' owed: ${rewardsQuote.rewards[0].rewardsOwed}`);
console.log(`Transaction ID: ${txId}`);
3. Usage Example
Suppose you are a developer creating a bot to manage investments for a group of investors. The bot periodically collects accumulated fees and rewards from liquidity positions to distribute profits among investors. By using the SDK, you can generate the instructions to collect earnings from each active position without closing it, allowing the liquidity to continue generating returns and potentially reinvest your earned fees into the position.
4. Next Steps
After harvesting fees and rewards, you can:
- Monitor Performance: Keep track of your position to evaluate future earnings and the overall performance.
- Reinvest Earnings: Use the harvested fees and rewards to add more liquidity or diversify your positions.
- Harvest Regularly: Regularly collect your earnings to maintain optimal capital efficiency while keeping your liquidity active.
By using the SDK, you can maximize the benefits of providing liquidity while keeping your position open and continuously earning fees.
As the liquidity pool is traded upon, liquidity providers will begin to accrue fees and rewards. Follow the following steps to see how much you are owe and how to collect them.
Get a quick quote on outstanding fees and rewards
There are use-cases where users would like to check the outstanding values before deciding to perform an on-chain update and harvest. In these cases, use the provided collectFeesQuote
and collectRewardsQuote
in the Typescript SDK.
// Fetching necessary on-chain account data.
const whirlpool = await fetcher.getPool(whirlpoolAddress);
const position = await fetcher.getPosition(positionAddress)
// Fetching tick array. Note that you may have to fetch two of them
// if the upper and lower ticks live on different tick arrays.
const tickArrayAddress = TickUtil.getPdaWithTickIndex(tickLowerIndex, ...);
const tickArray = await fetcher.getTickArray(tickArrayAddress);
// Get the individual TickData on each tickIndex from the fetched TickArray
const lowerTick = TickUtil.getTickFromTickArrayData(tickArrayData, tickLowerIndex, tickSpacing);
const upperTick = TickUtil.getTickFromTickArrayData(tickArrayData, tickUpperIndex, tickSpacing);
const feeQuote = collectFeesQuote({
whirlpool,
position,
tickLower: lowerTick,
tickUpper: upperTick,
});
const feesInTokenA = feeQuote.feeOwedA;
const feesInTokenB = feeQuote.feeOwedB;
const rewardQuote = collectRewardsQuote({
whirlpool,
position,
tickLower: lowerTick,
tickUpper: upperTick,
});
const rewardsInReward0 = rewardQuote[0].toNumber();
const rewardsInReward1 = rewardQuote[1].toNumber();
const rewardsInReward2 = rewardQuote[2].toNumber();
Update on-chain position with the latest accrued fees
Before you fetch your owed fees, you must update the on-chain position with the latest values by calling increase_liquidity
or decrease_liquidity
. Alternatively, you can call update_fee_and_rewards
to update without modifying liquidity.
If this step is skipped, the collect instructions will only fetch the last updated values of the position. In many cases, this will be 0.
Sample code on using update_fee_and_rewards
:
const whirlpool = await fetcher.getPool(whirlpoolAddress);
const position = await fetcher.getPosition(positionAddress);
const tickArrayLower = getTickArrayPda(ctx.program.programId, whirlpoolAddress, position.tickLowerIndex);
const tickArrayUpper = getTickArrayPda(ctx.program.programId, whirlpoolAddress, position.tickUpperIndex);
await toTx(ctx, WhirlpoolIx.updateFeesAndRewardsIx(ctx.program, {
whirlpool: position.whirlpool,
position: positionAddress,
tickArrayLower,
tickArrayUpper,
})).buildAndExecute();
Collect Fees and Rewards
Once the position has been updated, you can use collect_fees
and collect_reward
to harvest the position.
Collect fee
const whirlpool = await fetcher.getPool(whirlpoolAddress);
const position = await fetcher.getPosition(positionAddress);
const positionTokenAccount = await deriveATA(provider.wallet.publicKey, position.positionMint);
const tokenOwnerAccountA = await deriveATA(provider.wallet.publicKey, whirlpool.tokenMintA);
const tokenOwnerAccountB = await deriveATA(provider.wallet.publicKey, whirlpool.tokenMintB);
await toTx(ctx, WhirlpoolIx.collectFeesIx(ctx.program, {
whirlpool: whirlpoolAddress,
positionAuthority: provider.wallet.publicKey,
position: positionAddress,
positionTokenAccount,
tokenOwnerAccountA,
tokenOwnerAccountB,
tokenVaultA: whirlpool.tokenVaultA,
tokenVaultB: whirlpool.tokenVaultB
})).buildAndExecute();
Collect rewards
// Fetching rewards at reward index 0
const whirlpool = await fetcher.getPool(whirlpoolAddress);
const position = await fetcher.getPosition(positionAddress);
const rewardTokenMint = whirlpool.rewardInfos[0].mint;
const rewardOwnerAccount = await deriveATA(provider.wallet.publicKey, rewardTokenMint);
const positionTokenAccount = await deriveATA(provider.wallet.publicKey, position.positionMint);
await toTx(ctx, WhirlpoolIx.collectRewardIx(ctx.program, {
whirlpool: whirlpoolAddress,
positionAuthority: provider.wallet.publicKey,
position: positionAddress,
positionTokenAccount,
rewardOwnerAccount: rewardOwnerAccount,
rewardVault: whirlpool.rewardInfo[0].vault,
rewardIndex: 0,
})).buildAndExecute();